package com.flex.reflection.resolver;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.lang.reflect.*;

/**
 * 参数类型解析
 *
 * @author ganlt
 * @since 2019/09/24 9:21
 */
public class ParamterResolveType {
    /**
     * 解析属性字段类型
     *
     * @param field
     * @param type  来源对象类型
     * @return
     */
    public Type resolveFieldType(Field field, Type type) {
        Type fieldType = field.getGenericType();
        Class<?> declaringClass = field.getDeclaringClass();
        return resolveType(fieldType, type, declaringClass);
    }


    /**
     * 解析方法返回类型
     *
     * @param method
     * @param type
     * @return
     */
    public Type resolveReturnType(Method method, Type type) {
        Type returnType = method.getGenericReturnType();
        Class<?> declaringClass = method.getDeclaringClass();
        return resolveType(returnType, type, declaringClass);
    }

    /**
     * 解析参数类型
     *
     * @return
     */
    public Type[] resolveParamTypes(Method method, Type type) {
        Type[] paramTypes = method.getGenericParameterTypes();
        Class<?> declaringClass = method.getDeclaringClass();
        Type[] result = new Type[paramTypes.length];
        for (int i = 0; i < paramTypes.length; i++) {
            result[i] = resolveType(paramTypes[i], type, declaringClass);
        }
        return result;
    }

    /**
     * 类型解析方法
     *
     * @param type
     * @param srcType
     * @param declaringClass
     * @return
     */
    private Type resolveType(Type type, Type srcType, Class<?> declaringClass) {
        if (type instanceof TypeVariable) {
            return resolveTypeVar((TypeVariable<?>) type, srcType, declaringClass);
        } else if (type instanceof ParameterizedType) {
            return resolveParameterizedType((ParameterizedType) type, srcType, declaringClass);
        } else if (type instanceof GenericArrayType) {
            return resolveGenericArrayType((GenericArrayType) type, srcType, declaringClass);
        } else {
            return type;
        }
    }

    private Type resolveTypeVar(TypeVariable<?> typeVar, Type srcType, Class<?> declaringClass) {
        Type result;
        Class<?> clazz;
        if (srcType instanceof Class) {
            clazz = (Class<?>) srcType;
        } else if (srcType instanceof ParameterizedType) {
            clazz = (Class<?>) ((ParameterizedType) srcType).getRawType();
        } else {
            throw new RuntimeException("传入的参数必须是type或Class,实际传入:" + srcType.getClass());
        }

        if (clazz == declaringClass) {
            Type[] bounds = typeVar.getBounds();
            if (bounds.length > 0) {
                return bounds[0];
            }
            return Object.class;
        }

        Type superclass = clazz.getGenericSuperclass();
        result = scanSuperTypes(typeVar, srcType, declaringClass, clazz, superclass);
        if (result != null) {
            return result;
        }

        Type[] superInterfaces = clazz.getGenericInterfaces();
        for (Type superInterface : superInterfaces) {
            result = scanSuperTypes(typeVar, srcType, declaringClass, clazz, superInterface);
            if (result != null) {
                return result;
            }
        }
        return null;
    }

    private Type scanSuperTypes(TypeVariable<?> typeVar, Type srcType, Class<?> declaringClass, Class<?> clazz, Type superclass) {
        if (superclass instanceof ParameterizedType) {
            ParameterizedType parentType = (ParameterizedType) superclass;
            Class<?> parentClass = (Class<?>) parentType.getRawType();
            TypeVariable<?>[] parentTypeVars = parentClass.getTypeParameters();
            if (srcType instanceof ParameterizedType) {
                parentType = translateParentTypeVars((ParameterizedType) srcType, clazz, parentType);
            }
            if (declaringClass == parentClass) {
                for (int i = 0; i < parentTypeVars.length; i++) {
                    if (typeVar == parentTypeVars[i]) {
                        return parentType.getActualTypeArguments()[i];
                    }
                }
            }

            if (declaringClass.isAssignableFrom(parentClass)) {
                return resolveTypeVar(typeVar, parentType, declaringClass);
            }
        } else if (superclass instanceof Class && declaringClass.isAssignableFrom((Class<?>) superclass)) {
            return resolveTypeVar(typeVar, superclass, declaringClass);
        }

        return null;
    }

    private ParameterizedType resolveParameterizedType(ParameterizedType parameterizedType, Type srcType, Class<?> declaringClass) {
        Class<?> rawType = (Class<?>) parameterizedType.getRawType();
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        Type[] args = getTypes(srcType, declaringClass, actualTypeArguments);

        return new ParameterizedTypeImpl(rawType, null, args);
    }

    private Type[] getTypes(Type srcType, Class<?> declaringClass, Type[] actualTypeArguments) {
        Type[] args = new Type[actualTypeArguments.length];
        for (int i = 0; i < actualTypeArguments.length; i++) {
            if (actualTypeArguments[i] instanceof TypeVariable) {
                args[i] = resolveTypeVar((TypeVariable<?>) actualTypeArguments[i], srcType, declaringClass);
            } else if (actualTypeArguments[i] instanceof ParameterizedType) {
                args[i] = resolveParameterizedType((ParameterizedType) actualTypeArguments[i], srcType, declaringClass);
            } else if (actualTypeArguments[i] instanceof WildcardType) {
                args[i] = resolveWildcardType((WildcardType) actualTypeArguments[i], srcType, declaringClass);
            } else {
                args[i] = actualTypeArguments[i];
            }
        }
        return args;
    }

    private Type resolveGenericArrayType(GenericArrayType genericArrayType, Type srcType, Class<?> declaringClass) {
        Type componentType = genericArrayType.getGenericComponentType();
        Type resolveCompenentType = null;
        if (componentType instanceof TypeVariable) {
            resolveCompenentType = resolveTypeVar((TypeVariable<?>) componentType, srcType, declaringClass);
        } else if (componentType instanceof GenericArrayType) {
            resolveCompenentType = resolveGenericArrayType((GenericArrayType) componentType, srcType, declaringClass);
        } else if (componentType instanceof ParameterizedType) {
            resolveCompenentType = resolveParameterizedType((ParameterizedType) componentType, srcType, declaringClass);
        }

        if (resolveCompenentType instanceof Class) {
            return Array.newInstance((Class<?>) resolveCompenentType, 0).getClass();
        } else {
            return new GenericArrayTypeImpl(resolveCompenentType);
        }
    }

    private Type resolveWildcardType(WildcardType wildcardType, Type srcType, Class<?> declaringClass) {
        Type[] lowerBounds = resolveWildcardTypeBounds(wildcardType.getLowerBounds(), srcType, declaringClass);
        Type[] upperBounds = resolveWildcardTypeBounds(wildcardType.getUpperBounds(), srcType, declaringClass);
        return new WildcardTypeImpl(lowerBounds, upperBounds);
    }

    private Type[] resolveWildcardTypeBounds(Type[] bounds, Type srcType, Class<?> declaringClass) {
        Type[] result = getTypes(srcType, declaringClass, bounds);
        return result;
    }

    private ParameterizedType translateParentTypeVars(ParameterizedType srcType, Class<?> srcClass, ParameterizedType parentType) {
        Type[] parentTypeArgs = parentType.getActualTypeArguments();
        Type[] srcTypeArgs = srcType.getActualTypeArguments();
        TypeVariable<?>[] srcTypeVars = srcClass.getTypeParameters();
        Type[] newParentArgs = new Type[parentTypeArgs.length];
        boolean noChange = true;
        for (int i = 0; i < parentTypeArgs.length; i++) {
            if (parentTypeArgs[i] instanceof TypeVariable) {
                for (int j = 0; j < srcTypeVars.length; j++) {
                    if (srcTypeVars[j] == parentTypeArgs[i]) {
                        noChange = false;
                        newParentArgs[i] = srcTypeArgs[j];
                    }
                }
            } else {
                newParentArgs[i] = parentTypeArgs[i];
            }
        }
        return noChange ? parentType : new ParameterizedTypeImpl((Class<?>) parentType.getRawType(), null, newParentArgs);
    }

    @Data
    @AllArgsConstructor
    public static class ParameterizedTypeImpl implements ParameterizedType {
        private Class<?> rawType;
        private Type ownerType;
        private Type[] actualTypeArguments;
    }

    @Data
    @AllArgsConstructor
    public static class WildcardTypeImpl implements WildcardType {
        private Type[] lowerBounds;
        private Type[] upperBounds;
    }

    @Data
    @AllArgsConstructor
    public static class GenericArrayTypeImpl implements GenericArrayType {
        private Type genericComponentType;
    }


}

